﻿#region Copyright
/* © 2008 George Tsiokos, licensed under the Microsoft Reciprocal License (Ms-RL) */
#endregion
using System;
using System.Diagnostics;
using System.Net;
using System.Threading;
using Comet.Diagnostics;

namespace Comet.Net {
    public sealed class CometListener : IDisposable {

        #region Fields (5)

        private readonly HttpListener _httpListener;
        private long _waithandleTimeout = 120000;
        private Instrument _busWaitTime = new NullInstrument ();

        #endregion Fields

        #region Constructors (2)

        public CometListener (HttpListener httpListener) {
            _httpListener = httpListener;
        }

        public CometListener ()
            : this (new HttpListener ()) {
        }

        #endregion Constructors

        #region Properties (4)

        /// <summary>
        /// Returns true if the object is supported by the current operating system.
        /// </summary>
        public static bool IsSupported {
            get {
                return HttpListener.IsSupported;
            }
        }

        /// <summary>
        /// Gets the Uniform Resource Identifier (URI) prefixes handled by this listener.
        /// </summary>
        /// <exception cref="ObjectDisposedException">This object has been closed.</exception>
        public HttpListenerPrefixCollection Prefixes {
            get {
                return _httpListener.Prefixes;
            }
        }

        /// <summary>
        /// Gets or sets the default maximum amount of time to wait on the WaitHandle
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">long.MaxValue milliseconds is the maximum wait time</exception>
        public TimeSpan WaitHandleTimeout {
            get {
                return TimeSpan.FromMilliseconds (_waithandleTimeout);
            }
            set {
                try {
                    _waithandleTimeout = (long)value.TotalMilliseconds;
                }
                catch (InvalidCastException) {
                    throw new ArgumentOutOfRangeException ();
                }
            }
        }

        #endregion Properties

        #region Methods (7)


        // Public Methods (3) 

        public void Dispose () {
            ((IDisposable)_httpListener).Dispose ();
        }

        public void Start (CometNetHandler handler, int workerThreads) {
            _httpListener.Start ();
            for (int i = 0; i < workerThreads; i++) {
                IAsyncResult result = BeginListener (_httpListener, handler);
                if (result.CompletedSynchronously)
                    ListenerCallbackCore (result);
            }
        }

        public void Stop () {
            _httpListener.Stop ();
        }



        // Private Methods (4) 

        private IAsyncResult BeginListener (HttpListener listener, CometNetHandler handler) {
            return listener.BeginGetContext (
                ListenerCallback,
                new CometAsyncState (handler, listener));
        }

        private void BusCallback (object state, bool timedOut) {
            _busWaitTime.Stop ();
            CometAsyncState asyncState = (CometAsyncState)state;
            asyncState.Handler.HandleRequest (asyncState.Context, timedOut);
        }

        private void ListenerCallback (IAsyncResult result) {
            if (!result.CompletedSynchronously)
                ListenerCallbackCore (result);
        }

        private void ListenerCallbackCore (IAsyncResult result) {
            while (result != null) {
                CometAsyncState asyncState = (CometAsyncState)result.AsyncState;
                CometNetHandler handler = asyncState.Handler;
                HttpListener listener = asyncState.Listener;
                HttpListenerContext context;
                try {
                    context = listener.EndGetContext (result);
                    asyncState.Context = context;
                    context.Response.KeepAlive = true;
                }
                catch (HttpListenerException) {
                    break;
                }
                long waitHandleTimeout = _waithandleTimeout;
                if (handler.RequestReceived (context, ref waitHandleTimeout)) {
                    _busWaitTime.Start ();
                    ThreadPool.UnsafeRegisterWaitForSingleObject (
                        handler.GetWaitHandle (),
                        BusCallback,
                        asyncState,
                        waitHandleTimeout,
                        true);
                }
                else
                    NetTrace.VerboseDebug ("{0} RequestReceived returned false for {1}", handler, context);

                result = BeginListener (listener, handler);
                if (!result.CompletedSynchronously)
                    break;
            }
        }
        #endregion Methods

    }
}